import de.odysseus.el.ExpressionFactoryImpl;
import de.odysseus.el.util.SimpleContext;
import lombok.AllArgsConstructor;
import org.activiti.bpmn.model.*;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.task.Task;

import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
import java.util.*;


@AllArgsConstructor
public class PreviewProcessService {


	private RuntimeService runtimeService;

	private TaskService taskService;

	private RepositoryService repositoryService;


	public List<ApproveNode> getPreviewNodes(String taskId) {
		/**
		 * 获取代办工作信息
		 */
		Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
		//获取流程模型
		BpmnModel model = repositoryService.getBpmnModel(task.getProcessDefinitionId());
		//获取以后节点
		FlowElement flowElement = model.getFlowElement(task.getTaskDefinitionKey());
		//获取流程变量
		Map<String, Object> params = runtimeService.getVariables(task.getExecutionId());
		//保留拜访过的节点，防止死循环
		Set<String> visitedElements = new HashSet<>();
		//递归获取所有预测节点
		List<ApproveNode> approveNodes = visiteElement(flowElement, params, visitedElements);
		return approveNodes;
	}

	/**
	 * 递归获取预测节点列表
	 *
	 * @param flowElement
	 * @param params
	 * @param visitedElements
	 * @return
	 */
	private List<ApproveNode> visiteElement(FlowElement flowElement, Map<String, Object> params, Set<String> visitedElements) {
		String id = flowElement.getId();
		//如果之前拜访过的节点就不再拜访
		if (visitedElements.contains(id)) {
			return Collections.EMPTY_LIST;
		}
		visitedElements.add(id);
		List<ApproveNode> nodes = new ArrayList<>();
		//UserTask是理论的审批节点，如果是UserTask就能够退出到预测的节点中
		if (flowElement instanceof UserTask) {
			UserTask item = (UserTask) flowElement;
			nodes.add(new ApproveNode(item.getName(), this.executeExpression(item.getAssignee(), params, String.class)));
		}

		//获取所有的进口，也就是流程模型中的连线
		List<SequenceFlow> sequenceFlows = this.getElementSequenceFlow(flowElement);
		if (sequenceFlows == null || sequenceFlows.isEmpty()) {
			return nodes;
		}
		FlowElement nextElement = null;
		if (sequenceFlows.size() == 1 && sequenceFlows.get(0).getConditionExpression() == null) {
			/**
			 * 如果只有一条连线并且没有设置流转条件，间接获取连线指标节点作为下一节点
			 */
			nextElement = sequenceFlows.get(0).getTargetFlowElement();
		} else {
			for (SequenceFlow seq : sequenceFlows) {
				if (seq.getConditionExpression() == null) {
					/**
					 * 如果没有条件合乎，那么就取获取到的第一条条件为空的节点
					 */
					if (nextElement == null) {
						nextElement = seq.getTargetFlowElement();
					}
				} else {
					/**
					 * 计算条件
					 */
					boolean value = this.verificationExpression(seq.getConditionExpression(), params);
					if (value) {
						nextElement = seq.getTargetFlowElement();
						break;
					}
				}
			}
		}
		nodes.addAll(this.visiteElement(nextElement, params, visitedElements));
		return nodes;
	}

	/**
	 * 获取流程连线
	 *
	 * @param flowElement
	 * @return
	 */
	private List<SequenceFlow> getElementSequenceFlow(FlowElement flowElement) {
		if (flowElement instanceof FlowNode) {
			return ((FlowNode) flowElement).getOutgoingFlows();
		}
		return Collections.EMPTY_LIST;
	}

	/**
	 * 执行表达式计算
	 *
	 * @param expression
	 * @param variableMap
	 * @param returnType
	 * @param <T>
	 * @return
	 */
	private <T> T executeExpression(String expression, Map<String, Object> variableMap, Class<T> returnType) {
		if (expression == null) {
			return null;
		}
		ExpressionFactory factory = new ExpressionFactoryImpl();
		SimpleContext context = new SimpleContext();
		for (String k : variableMap.keySet()) {
			context.setVariable(k, factory.createValueExpression(variableMap.get(k), Object.class));
		}
		ValueExpression valueExpression = factory.createValueExpression(context, expression, returnType);
		return (T) valueExpression.getValue(context);

	}

	/**
	 * 验证表达式后果 true/false
	 *
	 * @param expression
	 * @param variableMap
	 * @return
	 */
	private Boolean verificationExpression(String expression, Map<String, Object> variableMap) {
		Boolean value = this.executeExpression(expression, variableMap, Boolean.class);
		return value == null ? false : value;
	}
}